package org.dromara.commission.listener.redisPlus;

import lombok.Data;
import java.util.*;

/**
 * @author 小将lcz
 * @version 1.0
 * @description 自定义Redis索引表
 * @date 2024/7/27 14:42
 */
@Data
public class RedisIndexTable {
    private int count = 0;//总记录数

    private String headStr = "";//自定义redis前缀串（不设置就是默认无）

    private String dataDirStr = "data:";//自定义数据目录（不设置就是默认"data:"）

    private String connectStr = "_";//自定义连接串（不设置就默认"_"）

    private String endStr = "end";//自定义连接串（不设置就默认"end"）

    public int ITEM_SIZE = 5000;//默认值为5000

    HashMap<String, Item> items = new HashMap<>();//所有key的内容列表

    public RedisIndexTable() {
    }
    public RedisIndexTable(String headStr) {
        this.headStr = headStr;
    }

    /***
    * @description 对应redis的一个key存储内容
    */
    @Data
    public static class Item{

        private int slice = 0;//当前item在keyHead里的第几片

        private Object flag = null;//标记对象（根据业务需求用来使用，可用作判断是否需要更新）

        private String key = "";//key
        private int size = 0;//该key的记录数

        private boolean isEnd = false;

        public Item() {}
        public Item(String key, int slice, int size, Object flag) {
            this.key = key;
            this.slice = slice;
            this.size = size;
            this.flag = flag;
        }
    }

    /***
    * @description 查询窗口
    */
    @Data
    public static class Window{
        private int beginOffset = 0;//首key偏移量量
        private int endIndex = 0;//最后一个的索引
        private int size = 0;//窗口大小
        private int total = 0;//符合条件的总数
        private List<Item> items = new ArrayList<>();//列表信息
    }

    /***
    * @description 给索引表添加item
    * @param item
    * @return
    */
    public String addItem(Item item){
        String key = this.headStr+dataDirStr+item.key+this.connectStr+item.slice;//构造key
        item.setKey(key);
        this.items.put(key,item);
        this.count += item.size; //每添加一个新的item进来count就变大
        return key;
    }

    /***
     * @description 给索引表添加尾片item
     * @param item
     * @return
     */
    public String addEndItem(Item item){
        String key = this.headStr+dataDirStr+item.key+this.connectStr+endStr;//构造尾部item的key
        item.setKey(key);
        item.setEnd(true);
        this.items.put(key,item);
        this.count += item.size; //每添加一个新的item进来count就变大
        return key;
    }

    /***
    * @description 获取indexTable存在redis中的key
    * @return String
    */
    public String getIndexTableKey(){
        return this.headStr+"indexTable";//构造尾部item的key
    }

    /***
     * @description 获取item级的key前缀
     * @return String
     */
    public String getItemPreKey(String key){
        return this.headStr+dataDirStr+key+this.connectStr;
    }

    /***
     * @description 获取尾片item的key
     * @return String
     */
    public String getItemEndKey(String key){
        return this.headStr+dataDirStr+key+this.connectStr+endStr;//构造尾片item的key
    }

    /***
     * @description 获取item的数字片key
     * @return String
     */
    public Item toSliceItem(Item item){
        this.items.remove(item.getKey());
        // 使用正则表达式替换末尾的 "_end"
        String regex = this.getConnectStr()+this.getEndStr()+"$";
        String newKey = item.getKey().replaceAll(regex, this.getConnectStr()+item.getSlice());//构造数字片item的key
        item.setKey(newKey);//刷新key
        item.setFlag(false);//去除尾片标记
        this.items.remove(item.getKey());//移除掉原来的 end,可以保证同一个keyHead的内部有序性
        this.items.put(newKey, item);
        return item;
    }

    /***
    * @description 获取查询窗口
    * @param begin
     * @param size
    * @return Window
    */
    public Window getWindow(int begin, int size){
        Window window = new Window();
        window.size = size;
        int index = 0; //item索引（第一个）
        for (Map.Entry<String, Item> entry : items.entrySet()) {
            Item item = entry.getValue();
            int endIndex = index+item.size-1;
            //获取窗口范围内的第一个key
            if(begin>=index && begin<=endIndex){
                window.beginOffset = begin - index;
            }
            //只要item里面有索引落在在(begin, begin+size)窗口里面的都要add
            if(endIndex >= begin && index<=begin+size){
                window.items.add(item);
            }
            index += item.size;//下一个item的索引
        }
        window.endIndex = window.beginOffset + size - 1;
        window.total = this.count;
        return window;
    }

    /***
    * @description 根据Redis的key前缀头keyHead来获取查询窗口
    * @param key
     * @param begin
     * @param size
    * @return Window
    */
    public Window getWindowByKeyHead(String key, int begin, int size){
        String keyHead = this.headStr+dataDirStr+key+this.connectStr;
        Window window = new Window();
        window.size = size;
        int index = 0;//item索引（第一个）
        for (Map.Entry<String, Item> entry : items.entrySet()) {
            Item item = entry.getValue();
            if(!item.key.startsWith(keyHead)){
                continue;
            }
            window.total += item.size;//加入总记录数

            int endIndex = index+item.size-1;//item最后一个元素的索引
            //获取第一个key
            if(begin>=index && begin<=endIndex){
                window.beginOffset = begin - index;
            }
            //只要item里面有索引落在在(begin, begin+size)窗口里面的都要add
            if(endIndex >= begin && index<=begin+size){
                window.items.add(item);
            }
            index += item.size;//下一个item的索引
        }
        window.endIndex = window.beginOffset + size - 1;
        return window;
    }

    /***
     * @description 根据Redis的key前缀头keyHead来获取所有记录的key
     * @param key
     * @return Window
     */
    public Window getWindowByKeyHead(String key){
        String keyHead = this.headStr+dataDirStr+key+this.connectStr;
        Window window = new Window();
        window.beginOffset = 0;
        for (Map.Entry<String, Item> entry : items.entrySet()) {
            Item item = entry.getValue();
            if(item.key.startsWith(keyHead)){
                window.total += item.size;//加入总记录数
                window.items.add(item);
            }
        }
        window.endIndex = window.total-1;
        return window;
    }

    /***
     * @description 删除前缀头keyHead的所有item
     * @param key
     * @return int
     */
    public int deleteItemByKeyHead(String key){
        String keyHead = this.headStr+dataDirStr+key+this.connectStr;//前缀头
        int delCount = 0;
        Set<String> keysToRemove = new HashSet<>();// 需要删除的键集合
        // 遍历并标记需要删除的元素（避免遍历的过程中修改HashMap,线程不安全）
        for (Map.Entry<String, Item> entry : items.entrySet()) {
            Item item = entry.getValue();
            if(item.getKey().startsWith(keyHead)){
                keysToRemove.add(item.getKey());
            }
        }

        // 执行实际的修改
        for (String removeKey : keysToRemove) {
            Item removeItem = this.items.remove(removeKey);
            this.count -= removeItem.getSize();//总数减少
            delCount += removeItem.getSize();
        }
        return delCount;
    }

    /***
     * @description 返回前缀头keyHead的所有item
     * @param key
     * @return List<Item>
     */
    public List<Item> findItemsByKeyHead(String key){
        String keyHead = this.headStr+dataDirStr+key+this.connectStr;
        List<Item> itemList = new ArrayList<>();
        for (Map.Entry<String, Item> entry : items.entrySet()) {
            Item item = entry.getValue();
            if(item.key.startsWith(keyHead)){
                itemList.add(item);
            }
        }
        return itemList;
    }
}
